﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;

namespace RoguelikeLib.Maps
{
    class Room
    {
        internal enum Sides
        {
            Left = 0,
            Top,
            Bottom,
            Right
        }
        internal class Feature
        {
            public int X { get; set; }
            public int Y { get; set; }
            public RObjects Type { get; set; }
        }

        internal class Door
        {
            internal enum DoorType
            {
                Wooden,
                Ice,
                Fire
            }
            internal enum DoorState
            {
                //Destroyed,
                //Open,
                Closed,
                //Locked
            }

            static Door()
            {
                foreach (var state in Enum.GetValues(typeof(DoorState)).Cast<DoorState>())
                {
                    string stateName = Enum.GetName(typeof(DoorState), state);
                    DoorRObjectsMap.Add(state, Enum.GetValues(typeof(DoorType)).Cast<DoorType>()
                                                   .ToDictionary(t => t,
                                                   t => (RObjects)Enum.Parse(typeof(RObjects), stateName + Enum.GetName(typeof(DoorType), t) + "Door")));
                }
            }

            private static readonly Dictionary<DoorState, Dictionary<DoorType, RObjects>> DoorRObjectsMap = new Dictionary<DoorState, Dictionary<DoorType, RObjects>>();

            public DoorState State { get; set; }
            public int X { get; set; }
            public int Y { get; set; }
            public DoorType Type { get; set; }

            public void AddToMap(StandardDungeon dungeon)
            {
                dungeon.Map[Y][X].Type = DoorRObjectsMap[State][Type];// RObjects.ClosedWoodenDoor;

            }
        }
        private readonly StandardDungeon _dungeon;
        public StandardDungeon Dungeon
        {
            get { return _dungeon; }
        }

        private readonly List<Door> _doors = new List<Door>();
        public List<Door> Doors
        {
            get { return _doors; }
        }

        private readonly List<Feature> _features = new List<Feature>();
        public List<Feature> Features
        {
            get { return _features; }
        }

        private readonly List<Corridor> _corridors = new List<Corridor>();
        public List<Corridor> Corridors
        {
            get { return _corridors; }
        }

        public int Left { get; set; }
        public int Top { get; set; }
        public int Right { get; set; }
        public int Bottom { get; set; }

        private int _sourceX, _sourceY;

        public Room(StandardDungeon dungeon, int x, int y)
        {
            _dungeon = dungeon;
            _sourceX = x;
            _sourceY = y;
        }

        public void AddRooms(int minRooms = 0)
        {
            int numberOfAttempts = Game.Roll(4, 1, 10);
            //int minRooms = 1;
            int numberOfRooms = 0;
            for (int i = 0; i < numberOfAttempts; i++)
            {
                CardinalDirection directionToBuild;
                var side = GetRandomSide();
                int x, y;
                Door door = new Door() { Type = Door.DoorType.Wooden };
                switch (side)
                {
                    case Sides.Left:
                        x = Left - 1;
                        y = Game.Roll(1, Top + 1, Bottom - 1);
                        door.X = Left;
                        door.Y = y;
                        directionToBuild = CardinalDirection.West;
                        break;
                    case Sides.Right:
                        x = Right + 1;
                        y = Game.Roll(1, Top + 1, Bottom - 1);
                        door.X = Right;
                        door.Y = y;
                        directionToBuild = CardinalDirection.East;
                        break;
                    case Sides.Top:
                        y = Top - 1;
                        x = Game.Roll(1, Left + 1, Right - 1);
                        door.X = x;
                        door.Y = Top;
                        directionToBuild = CardinalDirection.North;
                        break;
                    case Sides.Bottom:
                        y = Bottom + 1;
                        x = Game.Roll(1, Left + 1, Right - 1);
                        door.X = x;
                        door.Y = Bottom;
                        directionToBuild = CardinalDirection.South;
                        break;
                    default:
                        if (numberOfAttempts < minRooms)
                            i--;
                        continue;
                }
                if (i == numberOfAttempts - 1 && numberOfRooms < minRooms)
                    i--;
                var corridor = Corridor.FromPoint(Dungeon, x, y);
                if (corridor == null || corridor.Rooms.Count == 0)
                    continue;
                numberOfRooms += corridor.Rooms.Count;
                Console.WriteLine(corridor.Rooms.Count);
                Doors.Add(door);
                corridor.AddToMap();
                foreach (var room in corridor.Rooms)
                {
                    Console.WriteLine("L:{0}, R:{1}, T:{2} B:{3}", room.Left, room.Right, room.Top, room.Bottom);
                    room.AddToMap();
                }
                Corridors.Add(corridor);
            }
            foreach (var door in Doors)
            {
                door.AddToMap(_dungeon);
            }
        }

        /// <summary>
        /// Adds the carving onto the map
        /// </summary>
        public bool AddToMap()
        {
            // Make sure the space is solid
            //if (!IsClear())
            //    return false;

            // Carve out the space
            for (var y = Top; y <= Bottom; y++)
                for (var x = Left; x <= Right; x++)
                {
                    if ((y == Top || y == Bottom) || (x == Left || x == Right))
                        Dungeon.Map[y][x].Type = RObjects.FireWall;
                    else
                        Dungeon.Map[y][x].Type = RObjects.StoneFloor;
                }
            foreach (var feature in Features)
                Dungeon.Map[feature.Y][feature.X].Type = RObjects.StairsDown;

            foreach (var door in Doors)
                door.AddToMap(_dungeon);
            // Add to list of rooms
            Dungeon.Rooms.Add(this);

            return true;
        }

        public bool ContainsPoint(int x, int y)
        {
            return x >= Left && x <= Right && y >= Top && y <= Bottom;

        }
        bool IsClear()
        {
            for (var y = Top; y <= Bottom; y++)
                for (var x = Left; x <= Right; x++)
                    if (y < 2 || y >= Dungeon.Map.Length - 1
                        || x < 1 || x >= Dungeon.Map[y].Length - 1
                        || Dungeon.Map[y][x].Type != RObjects.BaseMaterial)
                        return false;
            return true;
        }


        # region Static

        public static Room CreateRoom(StandardDungeon dungeon, int startingX, int startingY)
        {
            return CreateRoom(dungeon, startingX, startingY, GetRandomDirection(), false);
        }

        public static Room CreateRoom(StandardDungeon dungeon, int startingX, int startingY, CardinalDirection directionToBuild, bool addDoor = true)
        {
            var room = new Room(dungeon, startingX, startingY);
            // Create first room
            int roomHeight = Math.Max(StandardDungeon.MinRoomHeight, Game.Roll(4, 0, 3));
            int roomWidth = Math.Max(StandardDungeon.MinRoomWidth, Game.Roll(5, 0, 3));
            int left, top, right, bottom;
            var door = new Door() { Type = Door.DoorType.Ice };

            if (directionToBuild == CardinalDirection.North)
            {
                bottom = startingY - 1;
                top = bottom - roomHeight;
                door.Y = bottom;
            }
            else if (directionToBuild == CardinalDirection.South)
            {
                top = startingY + 1;
                bottom = top + roomHeight;
                door.Y = top;
            }
            else
            {
                int startingSpot = Game.Roll(1, 1, roomHeight - 2);
                top = startingY - startingSpot;
                bottom = top + roomHeight;
                door.Y = startingY;
            }

            if (directionToBuild == CardinalDirection.West)
            {
                right = startingX - 1;
                left = right - roomWidth;
                door.X = right;
            }
            else if (directionToBuild == CardinalDirection.East)
            {
                left = startingX + 1;
                right = left + roomWidth;
                door.X = left;
            }
            else
            {
                int startingSpot = Game.Roll(1, 1, roomWidth - 2);
                left = startingX - startingSpot;
                right = left + roomWidth;
                door.X = startingX;
            }
            room.Left = left;
            room.Top = top;
            room.Right = right;
            room.Bottom = bottom;
            if (!room.IsClear())
                return null;
            if (addDoor)
                room.Doors.Add(door);
            Console.WriteLine("Door: {0}, {1}", door.X, door.Y);

            return room;
        }

        static CardinalDirection GetRandomDirection()
        {
            return (CardinalDirection)Game.Roll(1, 1, 4);
        }

        static Sides GetRandomSide()
        {
            return (Sides)Game.Roll(1, 0, 3);
        }

        # endregion
    }
}
